package com.huzz.util.log;

import com.alibaba.fastjson.JSON;
import com.huzz.util.security.SecurityUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.util.*;

/**
 * 系统日志收集逻辑
 *
 * @author 童年的纸飞机
 * @create 2022-10-09 11:30
 */
@Aspect
@Component
public class LogAspect {

    private long currentTime = 0L;

    private static final Logger logger = LoggerFactory.getLogger(LogAspect.class);

    @Autowired
    private Map<String, LogDetailsService> logDetailsServiceMap;

    @Autowired
    private SysLogManager sysLogBuilder;

    /**
     * @param joinPoint
     * @param log
     * @return
     * @throws Throwable
     */
    @Around("@annotation(log)")
    public Object doRunning(ProceedingJoinPoint joinPoint, Log log) throws Throwable {
        // 响应信息获取
        Object result = joinPoint.proceed();
        // 日志收集逻辑
        handleLog(joinPoint, log, null, result);

        return result;
    }

    /**
     * If Exists Error
     *
     * @param joinPoint
     * @param log
     * @param e
     */
    @AfterThrowing(pointcut = "@annotation(log)", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Log log, Throwable e) {
        handleLog(joinPoint, log, e, null);
    }


    public void handleLog(JoinPoint joinPoint, Log log, Throwable e, Object result) {
        SysLog sysLog = sysLogBuilder.getSysLogEntity();

        sysLog.setModuleName(getCurrentModule(joinPoint));
        sysLog.setBusinessName(log.name());
        sysLog.setOperateType(log.operateType().getValue());
        sysLog.setOperateUserId(getCurrentUserId());
        sysLog.setRequestIp(getRequestIp());
        sysLog.setStatus(e == null ? true : false);
        sysLog.setExpMessage(e == null ? "" : throwableStackTrace(e));
        sysLog.setRequestParams(log.isSave() && log.saveReqParam() ? saveReqParam(joinPoint) : "");
        sysLog.setResponseParams(log.isSave() && log.saveResResult() ? JSON.toJSONString(result) : "");
        sysLog.setLogDetails(getCurrentLogDetails(joinPoint, sysLog, log));
        sysLog.setCreatedTime(new Date());

        sysLogBuilder.saveLog(sysLog);

        logger.info("---- [{}]日志已生成 ----", getCurrentModule(joinPoint));

    }

    /**
     * Get module name
     *
     * @param joinPoint
     * @return
     */
    private String getCurrentModule(JoinPoint joinPoint) {
        LogStarter logStart = joinPoint.getTarget().getClass().getAnnotation(LogStarter.class);
        if (logStart == null) {
            return "";
        }

        return logStart.value();
    }


    private String getCurrentLogDetails(JoinPoint joinPoint, SysLog sysLog, Log log) {
        // 获取自定义收集的LogDetails实例
        String LogDetailsBeanName = lowerFirstCase(joinPoint.getTarget().getClass().getSimpleName().replace("Controller", "LogDetailsServiceImpl"));
        LogDetailsService logDetailsService = Optional.ofNullable(logDetailsServiceMap.get(LogDetailsBeanName))
                .orElse(logDetailsServiceMap.get("defaultLogDetailsServiceImpl"));

        Object requestParams = logDetailsService.requestParams(sysLog.getModuleName(), log.operateType(), joinPoint.getArgs());
        List<Map<String, Object>> mapList = logDetailsService.setLogDetails(sysLog, sysLog.getModuleName(), log.operateType(), requestParams);

        return JSON.toJSONString(mapList);
    }

    private String lowerFirstCase(String str) {
        char[] chars = str.toCharArray();

        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * 异常信息
     *
     * @param throwable
     * @return
     */
    public static String throwableStackTrace(Throwable throwable) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);

        String var3;
        try {
            throwable.printStackTrace(pw);
            var3 = sw.toString();
        } finally {
            pw.close();
        }
        return var3;
    }

    private String saveReqParam(JoinPoint joinPoint) {
        List<String> params = new ArrayList<>();
        Object[] pointArgs = joinPoint.getArgs();

        if (pointArgs != null && pointArgs.length > 0) {
            for (Object o : pointArgs) {
                if (o != null && (
                                // 过滤不能序列化的参数
                                hasField(o, "response")
                                || hasField(o, "request")
                                || hasField(o, "filename")
                                || hasField(o, "outputStream")
                                || hasField(o, "inputStream"))) {
                    continue;
                }else {
                    params.add(JSON.toJSONString(o));
                }
            }

        }

        return JSON.toJSONString(params);
    }

    /**
     * 判断对象是否存在某字段
     *
     * @param entity
     * @param fieldName
     * @return
     */
    private boolean hasField(Object entity, String fieldName) {
        Field[] fields = entity.getClass().getDeclaredFields();
        try {
            for (Field f : fields) {
                if (fieldName.equals(f.getName())) {
                    return true;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * Get Request IP
     * @return
     */
    private String getRequestIp() {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        return request.getRemoteAddr();
    }

    private String getCurrentUserId() {
        return SecurityUtil.getCurrentUserId();
    }

}
